// ml278
// Copyright (C) 2008 Bjørn Øivind Bjørnsen, Aleksander Gjermundsen, Knut Andre Tidemann, Thomas Hansen
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

#include <assert.h>
#include "planetarypage.h"
#include "planetaryscenemanager.h"
#include "planetaryoptions.h"
#include "planetarytile.h"
#include "planetaryzone.h"
#include "planetarypagerenderable.h"
#include "planetaryplanet.h"

namespace Ogre
{
	PlanetaryPage::PlanetaryPage(PlanetarySceneManager *mgr) : PlanetaryLoadable(mgr)
	{
		mSceneMgr = mgr;
		mSceneNode = NULL;
		mRenderable = NULL;

		mParent = NULL;

		for(int i = 0; i < 4; i++)
			mNeighbours[i] = NULL;



		mIsLoading = false;
		mIsUnloading = false;
		mIsLoaded = false;
		mIsInit = false;
		mVisible = false;

		mZone = 0;
		mPageX = 0;
		mPageZ = 0;
	}

	PlanetaryPage::~PlanetaryPage()
	{
		if(mIsLoaded)
			unload();
		if(mIsInit)
			uninit();
	}

	void PlanetaryPage::init(unsigned int zone, unsigned int pageX, unsigned int pageZ, PlanetaryZone *parent)
	{
		assert(mTiles.empty());
		assert(!mIsInit);

		mZone	= zone;
		mPageX	= pageX;
		mPageZ	= pageZ;

		mParent = parent;

		mTiles.reserve(mOptions->PageSize);
		mTiles.resize(mOptions->PageSize);

		// Create the LOD renderable
		mRenderable = new PlanetaryPageRenderable(mSceneMgr);
		mRenderable->init(mZone,mPageX,mPageZ,this);

		
		// Create scene node
		if(!mSceneNode)
		{
			const String name = "Node::" + StringConverter::toString(mZone) + "::" + 
				StringConverter::toString(mPageX) + "." + StringConverter::toString(mPageZ);

			mSceneNode = mParent->getSceneNode()->createChildSceneNode(name);
		}

		// Create tile objects
		PlanetaryTile *t;

		for(unsigned int i = 0; i < mOptions->PageSize; i++)
		{
			mTiles[i].reserve(mOptions->PageSize);
			mTiles[i].resize(mOptions->PageSize);

			for(unsigned int j = 0; j < mOptions->PageSize; j++)
			{
				t = new PlanetaryTile(mSceneMgr);
				t->init(mZone,mPageX,mPageZ,i,j,this);

				mTiles[i][j] = t;
			}
		}

		mIsInit = true;
	}

	void PlanetaryPage::load()
	{
		assert(!mIsLoaded);
		assert(mIsInit);

		// Init lod-joints
		for(int i = 0; i < 4; i ++)
		{
			if(mNeighbours[i] == NULL)
			{
				mLODJoints[i] = NULL;
				continue;
			}

			mLODJoints[i] = new PlanetaryPageLODJoint(mSceneMgr);
			mLODJoints[i]->init(0,mZone,mPageX,mPageZ,mNeighbours[i]->mZone,mNeighbours[i]->mPageX,mNeighbours[i]->mPageZ,(NEIGHBOUR)i,mSceneNode);
		}

		mRenderable->load();
		mRenderable->setVisible(false);

		// Load the LOD-joints
		for(int i = 0; i < 4; i ++)
			if(mLODJoints[i] != NULL)
				mLODJoints[i]->load();

		LogManager::getSingleton().logMessage("Page loaded: " + StringConverter::toString(mZone) + "::" + 
			StringConverter::toString(mPageX) + "." + StringConverter::toString(mPageZ));

		touch();

		mIsLoaded = true;
	}

	void PlanetaryPage::unload()
	{
		assert(mIsLoaded);

		mRenderable->unload();

		for(unsigned int i = 0; i < mOptions->PageSize; i++)
		{
			for(unsigned int j = 0; j < mOptions->PageSize; j++)
			{
				if(mTiles[i][j]->isLoaded())
				{
					if(!mTiles[i][j]->isUnloading())
					{
						mParent->getParent()->addLoadableToUnloadQueue(mTiles[i][j]);
					}
				}
			}
		}

		LogManager::getSingleton().logMessage("Page unloaded: " + StringConverter::toString(mZone) + "::" + 
			StringConverter::toString(mPageX) + "." + StringConverter::toString(mPageZ));

		mIsLoaded = false;
	}

	void PlanetaryPage::uninit()
	{
		assert(mIsInit);
		assert(!mIsLoaded);

		mTiles.clear();

		mRenderable->uninit();
		delete mRenderable;
		mRenderable = NULL;

		mSceneNode->removeAndDestroyAllChildren();

		mIsInit = false;
	}

	void PlanetaryPage::updatePaging(Camera *cam)
	{
		if(!mVisible)
			return;

		Sphere camsphere,camloadsphere;

		camsphere.setCenter(cam->getRealPosition());
		camsphere.setRadius(mOptions->mPageRenderableLODDistance);

		camloadsphere.setCenter(cam->getRealPosition());
		camloadsphere.setRadius(mOptions->mPageRenderableLODDistance * 1.5); // Multiply by 1.5 of the LOD change distance

		if(mRenderable->getBoundingBox().intersects(camloadsphere))
		{
			// Load the tiles that's not already loaded
			for(std::vector<std::vector<PlanetaryTile *> >::iterator i = mTiles.begin(); i != mTiles.end(); i++)
			{
				for(std::vector<PlanetaryTile *>::iterator j = (*i).begin(); j != (*i).end(); j++)
				{
					if(!(*j)->isLoaded() && !(*j)->isLoading())
					{
						// Make sure we insert tiles infront of pages
						mParent->getParent()->insertLoadableToLoadQueue(*j);
					}

					(*j)->touch();
				}
			}

			if(mRenderable->getBoundingBox().intersects(camsphere))
			{
				// Check if we're displaying the page renderable
				if(mRenderable->isVisible())
				{
					// Make sure all tiles are already loaded before we try to view them
					for(std::vector<std::vector<PlanetaryTile *> >::iterator i = mTiles.begin(); i != mTiles.end(); i++)
					{
						for(std::vector<PlanetaryTile *>::iterator j = (*i).begin(); j != (*i).end(); j++)
						{
							if(!(*j)->isLoaded())
								return;
						}
					}

					// Hide the page and display tiles
					mRenderable->setVisible(false);
					mSceneNode->detachObject(mRenderable);

					for(std::vector<std::vector<PlanetaryTile *> >::iterator i = mTiles.begin(); i != mTiles.end(); i++)
					{
						for(std::vector<PlanetaryTile *>::iterator j = (*i).begin(); j != (*i).end(); j++)
						{
							if(!(*j)->isLoaded())
								(*j)->load();
							(*j)->show(true);
						}
					}

					// Notify self first
					_notifySelf();

					NEIGHBOUR n;
					for(int i = 0; i < 4; i++)
					{
						if(mNeighbours[i] == NULL)
							continue;

						// Translate into the correct relation if the neighbours are in different zones, if not just invert it.
						if(mZone != mNeighbours[i]->mZone)
							n = translateNeighbour(mZone,(NEIGHBOUR)i);
						else
							n = (NEIGHBOUR)(i + 1 - ((i%2)*2));
						mNeighbours[i]->_notifyNeighbour(n,this);
					}
				}
			}
			else
			{
				if(!mRenderable->isVisible())
				{
					// Hide tiles and display page
					mRenderable->setVisible(true);
					mSceneNode->attachObject(mRenderable);

					for(std::vector<std::vector<PlanetaryTile *> >::iterator i = mTiles.begin(); i != mTiles.end(); i++)
					{
						for(std::vector<PlanetaryTile *>::iterator j = (*i).begin(); j != (*i).end(); j++)
						{
							(*j)->show(false);
							//(*j)->unload();
						}
					}

					// Notify self first
					_notifySelf();

					NEIGHBOUR n;
					for(int i = 0; i < 4; i++)
					{
						if(mNeighbours[i] == NULL)
							continue;

						// Translate into the correct relation if the neighbours are in different zones, if not just invert it.
						if(mZone != mNeighbours[i]->mZone)
							n = translateNeighbour(mZone,(NEIGHBOUR)i);
						else
							n = (NEIGHBOUR)(i + 1 - ((i%2)*2));
						mNeighbours[i]->_notifyNeighbour(n,this);
					}
				}
			}
		}
		else
		{
			// Unload tiles that's not needed anymore
			for(std::vector<std::vector<PlanetaryTile *> >::iterator i = mTiles.begin(); i != mTiles.end(); i++)
			{
				for(std::vector<PlanetaryTile *>::iterator j = (*i).begin(); j != (*i).end(); j++)
				{
					if(((*j)->isLoaded() || (*j)->isLoading()) && (*j)->unloadUntouched() && !(*j)->isUnloading())
					{
						mParent->getParent()->addLoadableToUnloadQueue(*j);
					}
				}
			}
		}
	}
	
	void PlanetaryPage::show(bool do_it)
	{
		mVisible = do_it; 

		if(!do_it)
		{
			if(mRenderable->isVisible())
			{
				mRenderable->setVisible(false);
				mSceneNode->detachObject(mRenderable);
			}

			for(std::vector<std::vector<PlanetaryTile *> >::iterator i = mTiles.begin(); i != mTiles.end(); i++)
			{
				for(std::vector<PlanetaryTile *>::iterator j = (*i).begin(); j != (*i).end(); j++)
					(*j)->show(false);
			}
		}
		else if(!mRenderable->isVisible())
		{
			mRenderable->setMaterial(mParent->getMaterial());
			mRenderable->setVisible(true);
			mSceneNode->attachObject(mRenderable);
		}
	}

	void PlanetaryPage::setMaterial(MaterialPtr mat)
	{
		mRenderable->setMaterial(mat);

		// If this page's renderable is not visible, set the material to the child-tiles.
		if(!mRenderable->isVisible())
		{
			for(unsigned int i = 0; i < mTiles.size(); i++)
				for(unsigned int j = 0; j < mTiles[i].size(); j++)
					mTiles[i][j]->setMaterial(mat);
		}
	}

	void PlanetaryPage::setNeighbour(NEIGHBOUR n, PlanetaryPage *p)
	{
		mNeighbours[n] = p;
	}

	void PlanetaryPage::_notifyNeighbour(NEIGHBOUR n,PlanetaryPage *callingNeighour)
	{
		if(callingNeighour == NULL)
			return;

		// Check if there neighbours have the same LOD level visible
		if(callingNeighour->mRenderable->isVisible() == mRenderable->isVisible())
		{
			// Hide joints
			if(mLODJoints[n] != NULL)
				mLODJoints[n]->show(false);
		}
		else
		{
			if(mLODJoints[n] == NULL)
				return;

			// Check if we're in the position to display lod-joints
			if(!mRenderable->isVisible())
			{
				mLODJoints[n]->show(false);
			}
			else
			{
				mLODJoints[n]->show(true);
			}
		}
	}

	void PlanetaryPage::_notifySelf()
	{
		for(unsigned int i = 0; i < 4; i ++)
		{
			if(mNeighbours[i] == NULL)
				continue;

			if(!mNeighbours[i]->mRenderable->isVisible() && mRenderable->isVisible())
			{
				if(!mLODJoints[i]->isVisible())
					mLODJoints[i]->show(true);
			}
			else if(!mNeighbours[i]->mRenderable->isVisible() && !mRenderable->isVisible())
			{
				if(mLODJoints[i]->isVisible())
					mLODJoints[i]->show(false);
			}
		}
	}
}
